
/***************************************************************************
 *   Copyright (C) 1997 to 2004 by Jonathan Duddington                     *
 *   email: jonsd@users.sourceforge.net                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write see:                           *
 *               <http://www.gnu.org/licenses/>.                           *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/* Bulk operations on article lists:
   set status, score, source, categories, subject,
   expire, remove attachments, unlink thread
*/

#include "werr.h"
#include "menu.h"
#include "wimp.h"
#include "dbox.h"
#include "msgs.h"

#include "narc.h"
#include "hdrs.h"


extern OPTIONS options;
extern FOLDREC list_fr[];
extern BLIST newsglist;
extern BLIST mlistlist;
extern FOLDREC cty_fr;
extern FOLDREC src_fr;
extern char *sort_card_file;
extern CARD_HEADER card_header;
extern BOX box_table[];
extern int boxes_unread_count[N_BOXES];
extern int dbox_menu_field;
extern int  today_date;
extern int  lock_expiry;
extern int select_user_flag;
extern int  main_index_sorted1;
extern char menu_score_value[];
extern TEXTR text_rec_temp;
extern char boxes_unread[N_BOXES];  /* highest priority status for this box */

static dbox dbox_set_source;
static FOLDREC *fr_set_source;
static int list_set_source_value;
static int list_set_source_action;




#define EXPIRE_BACKG
static int cancel_expiry;


void dbox_expire_handler(dbox d, void *handle)
/********************************************/
{
	switch(dbox_get(d))
	{
	case 0:   /* Cancel expiry */
		cancel_expiry = 1;
		break;
	}
}   /* end of dbox_expire_handler */



void expire_articles(int start)
/*****************************/
/* Delete any articles which have expired
   Don't expire any articles in a thread until they are all ready for expiry

   start=0  continue
         1  start
         2  start, override once-per-day and inhibit-expiry checks
*/
{
	static unsigned int *ixlist=NULL;
	static int  expiry_ix;
	static int  today_date1;
	static int  today_year;
	static int  n_entries;
	static int  n_cards;
	static char *source_expiry=NULL;
	static dbox d;

	CARD *cptr;
	FOLDREC *fr;
	int  box;
	int  i;
	int  thread_start;
	int  thread_end;
	int  level;
	static int  expiry;
	static int  start_code;
	int  expire_box;
	int  expiry_date;
	int  expiry_year;
	int  box_expiry;
	int  status;
	int  result;
	FILE *f;
	NEWSG **ng_index;
	char buf[32];

	/* ???
	   Could something else be setting the Marked bit during multi-threading and
	   causing spurious expiries ? */


	if(check_lock_lists(0xffff) != 0)
	{
		expiry_ix = n_entries;
		return;
	}

	if(start > 0)
	{
		if(lock_expiry != 0)
			return;

		today_date = get_today_date();

		if(start == 1)
		{
			if((options.allow_expiry == 0) || (today_date == card_header.today_date))
			{
				null_events(4,0);
				return;
			}
		}

		/* update the card header to prevent expire running again if we
		   crash part way through */
		card_header.today_date = today_date;
		f = fopen_pluto("Articles.Cards","r+");
		if(f!=NULL)
		{
			fwrite(&card_header,1,sizeof(card_header),f);
			fclose(f);
		}

		/* make table of expiries for each source number */
		source_expiry = calloc(256,1);
		if(source_expiry == NULL)
		{
			malloc_err(5);
			return;
		}
		ng_index = (NEWSG **)(newsglist.index_ptr);
		for(expiry_ix=0; expiry_ix<(newsglist.n_entries+mlistlist.n_entries); expiry_ix++)
		{
			source_expiry[ng_index[expiry_ix]->source] = ng_index[expiry_ix]->expiry;
		}

		/* have any boxes got expiry set ? */
		i=0;
		for(box=0; box<BOX_EXTERN; box++)
		{
			if(box_table[box].name[0] != 0)
			{
				if((box_table[box].expiry > 0) ||
						(box_table[box].expiry_flags & BOX_NEWSG_EXPIRY))
					i=1;
			}
		}
		if(i==0)
		{
			if(start == 2)
			{
				/* manual expiry */
				werr(0,"No Boxes have expiry options set");
			}
			return;   /* no expiry set on any boxes */
		}

		today_year = today_date >> 18;
		today_date1 = (today_date & 0x3ffff) / 576;     /* day of year */

		n_cards = 0;

		fr = &list_fr[0];
		n_entries = fr->n_entries;

		if((ixlist = malloc(sizeof(int)*(n_entries+1))) == NULL)
		{
			malloc_err(6);
			return;
		}

		d = dbox_new("Message");   /* "Message" */
		dbox_eventhandler(d, dbox_expire_handler, (void *)fr);
		dbox_setfield(d,1,"Expiring Articles");
		dbox_showstatic(d);
		cancel_expiry = 0;
		call_event_process(10);
		/*      set_focus(dbox_syshandle(d)); */

		lock_expiry = 1;
		start_code = start;
		visdelay2_begin();

		sort_card_file = 0;

		if(fr->ixlist == NULL)
			return;

		qsortG(fr->ixlist,n_entries,sizeof(int *),box_sorter2);  /* sort by box, ng, title, date */
		main_index_sorted1 = 0;

		call_event_process(0);
		index_diff_levels_expire(fr);
		call_event_process(0);

		memcpy(ixlist,fr->ixlist,sizeof(int)*n_entries);


		/* look at each thread to see whether it was expired */
		for(expiry_ix=0; expiry_ix<n_entries;)
		{
			if((expiry_ix & 0xf)==0)
				call_event_process(0);

			if(cancel_expiry)
				break;

			thread_start = expiry_ix;
			do
			{
				ixlist[expiry_ix] &= ~IXLIST_MARKED;
				expiry_ix++;
			} while((expiry_ix < n_entries) && ((level = ixlist[expiry_ix] >> IXLEV) >= 2));

			thread_end = expiry_ix;   /* the entry after the end of the thread */

			/* look at the last (latest) item in this thread */
			cptr = (CARD *)((ixlist[expiry_ix-1] & IXLIST_MASK) << 2);
			if((box = cptr->date_box >> 26) >= BOX_BIN)
				continue;   /* already in BIN */

			/* look at box expiry period, unless the box's newsg_expiry option
			   is set, in which case look at the newsgroup expiry */

			box_expiry = 0;
			if(box_table[box].expiry_flags & BOX_NEWSG_EXPIRY)
			{
				box_expiry = source_expiry[cptr->source];
			}

			if(box_expiry == 0)
			{
				/* no period set for the newsgroup, or ignore it for this box */
				box_expiry = box_table[box].expiry;
			}
			if(box_expiry == 0)
				continue;

			expiry_year = today_year;
			expiry_date = today_date1 - box_expiry;
			while(expiry_date < 0)
			{
				expiry_year--;
				expiry_date += 365;
			}

			expiry = (expiry_year << 18) + (expiry_date * 576);

			if((box_table[box].sort_on == 0) && (options.expire_active_threads == 0))
			{
				/* standard newsgroups sort, expire the whole thread if the last item has expired */

				if((cptr->date_box & DATE_MASK) < expiry)
				{
					/* the latest article in this thread has expired, mark the
					   whole thread for expiry */

					for(i=thread_start; i<thread_end; i++)
					{
						ixlist[i]  |= IXLIST_MARKED;
					}
				}
			}
			else
			{
				/* check each article individually for expiry */
				for(i=thread_start; i<thread_end; i++)
				{
					cptr = (CARD *)((ixlist[i] & IXLIST_MASK) << 2);
					if((cptr->date_box & DATE_MASK) < expiry)
					{
						ixlist[i] |= IXLIST_MARKED;
					}
				}
			}
		}


		/* sort by article address */
		call_event_process(0);

		if(cancel_expiry==0)
		{
			sort_card_file = 0;
			qsortG(ixlist,n_entries,sizeof(int *),article_sorter);
			expiry_ix = 0;
		}
		call_event_process(0);

#ifdef EXPIRE_BACKG
		null_events(4,1);
		visdelay2_end();
#endif
	}

	for(; expiry_ix<n_entries; expiry_ix++)
	{
		if(cancel_expiry || ((options.allow_expiry == 0) && (start_code != 2)))
		{
			expiry_ix = n_entries;
			werr(0,"Expiry abandoned");
			break;
		}
		if(ixlist[expiry_ix] & IXLIST_MARKED)
		{
			cptr = (CARD *)((ixlist[expiry_ix] & IXLIST_MASK) << 2);

			status = cptr->status & STATUS_MASK;

			box = cptr->date_box >> 26;

			if((status > STATUS_UNREAD) && (status < STATUS_READ) &&
					((box_table[box].expiry_flags & BOX_EXPIRE_LOCKED)==0))
			{
				continue;    /* locked */
			}

			if((status <= STATUS_UNREAD) && ((box_table[box].flags & BOX_EXPIRE_UNREAD)==0))
				continue;   /* don't expire unread articles in this box */

			n_cards++;

			expire_box = box_table[box].expire_to;
			cptr->date_box = (cptr->date_box & ~BOX_MASK) + (expire_box << 26);

			result = 0;
			if(expire_box != BOX_BIN)
			{
				if(box_table[expire_box].flags & BOX_STRIP_HEADERS)
					result = article_delete_internet_header(NULL,cptr,1);
				else
					result = article_update(NULL,cptr,NULL,0,0);
			}
			else
			{
				result = article_delete(NULL,cptr,1,0);
			}

			if(result != 0)
			{
				expiry_ix = n_entries;
				werr(0,"Expiry abandoned");
			}

#ifdef EXPIRE_BACKG
			expiry_ix++;
			return;   /* wait until next null event */
#endif
		}
	}

	/* finished scanning all the articles */
#ifdef EXPIRE_BACKG
	null_events(4,0);
#else
	visdelay2_end();
#endif

	dbox_dispose(&d);
	sprintf(buf,msgs_lookup("Y15"),n_cards);
	announce_message(buf,1);  // n articles expired


	if((lock_expiry) && (ixlist != NULL))
	{
		free(source_expiry);
		free(ixlist);
		ixlist = NULL;
	}
	lock_expiry = 0;

	if(select_user_flag == 0)
		set_boxlist_extent(7);   /* open boxlist window */

	if(main_index_sorted1 == 0)
	{
		sort_msgids();
	}


	if(pluto_run_options != 0)
	{
		/* we were waiting for the expiry to finish before performing a
		   debatch */
		pluto_wimp_message(pluto_run_options,pluto_run_options2,NULL);
		pluto_run_options = 0;
	}
}   /* end of expire_articles */








void list_set_status(FOLDREC *fr, int status)
/*******************************************/
/* Values 0,1,2,3 set status
   STATUS_UNREAD  read,locked -> unread
   STATUS_MARKED  any -> marked
   STATUS_LOCKED  any -> locked
   STATUS_READ    unread -> read
   Value 0x200 + x, set colour
*/
{
	int  new_status;
	int  new_replied;
	int  new_outgoing;
	int  new_box;
	CARD *cptr;
	int  stat;
	int  replied;
	int  outgoing;
	int  box;
	int  box_status_changed=0;
	int  changed_box=-1;  /* -1: not set,  -2: more than one box */
	int  dont_recount = 0;

	int boxes_unread_copy[N_BOXES];

	new_box = (status & 0xff) << 26;

	if(check_lock_lists(0xffff) != 0)
		return;

	set_lock_lists(3,fr);

	/* take a copy to avoid redraws while updating */
	memcpy(boxes_unread_copy,boxes_unread_count,sizeof(boxes_unread_copy));

	list_enumerate_start(fr);

	while((cptr = list_enumerate_next(fr,fr->ixlist)) != NULL)
	{
		new_status = -1;
		new_replied = -1;
		new_outgoing = -1;

		if((cptr->status & STATUS_MASK2) == STATUS_DRAFT)
			continue;

		/* don't change the state back to "Read" when we've finished reading */
		card_changed_status(cptr);

		stat = cptr->status & STATUS_MASK;
		replied = cptr->status & STATUS_BIT_REPLIED;
		outgoing = cptr->status & STATUS_BIT_OG;

		switch(status)
		{
		case STATUS_NEW:
		case STATUS_UNREAD:
			if(stat > STATUS_UNREAD)
				new_status = STATUS_UNREAD;
			break;

		case STATUS_MARKED:
			if(stat != STATUS_MARKED)
				new_status = STATUS_MARKED;
			break;

		case STATUS_LOCKED:
			if(stat != STATUS_LOCKED)
				new_status = STATUS_LOCKED;
			break;

		case STATUS_READ:
			if(stat < STATUS_READ)
				new_status = STATUS_READ;
			break;

		case 10:   /* change unread to read */
			if((stat <= STATUS_UNREAD) && ((cptr->status & STATUS_MASK2) != STATUS_FETCHING))
				new_status = STATUS_READ;
			break;

		case 20:   /* set replied */
			if(stat <= STATUS_UNREAD)
			{
				new_status = STATUS_READ;
				replied = 0;
			}

			if(replied == 0)
			{
				new_replied = STATUS_BIT_REPLIED;
			}
			break;

		case 21:   /* unset replied */
			if(replied && (stat != (STATUS_FETCHING & STATUS_MASK)))
			{
				new_replied = 0;
			}
			break;

		case 30:   /* set outgoing */
			if(outgoing == 0)
			{
				new_outgoing = STATUS_BIT_OG;
			}
			break;

		case 31:   /* unset outgoing */
			if(outgoing)
			{
				new_outgoing = 0;
			}
			break;

		default:
			if((status & 0xf00) == 0x200)
			{
				/* set colour */
				cptr->alength = (cptr->alength & ART_LENGTH_MASK) + ((status & 0x7) << 24);
				article_update(fr,cptr,NULL,0,0);

				call_event_process(0);
			}
		}

		if((new_status >= 0) || (new_replied >= 0) || (new_outgoing >= 0))
		{
			if(new_status >= 0)
			{
				if((cptr->status & STATUS_MASK2) == STATUS_FETCHING)
					cptr->status &= ~STATUS_BIT_REPLIED;  /* moving from FETCHING status, clear replied bit */

				cptr->status = (cptr->status & ~STATUS_MASK) + new_status;

				/* adjust unread count for this box */
				box = cptr->date_box >> 26;
				if(changed_box == -1)
					changed_box = box;
				else if(box != changed_box)
					changed_box = -2;

				if((stat <= STATUS_UNREAD) && (new_status > STATUS_UNREAD))
				{
					if(boxes_unread_copy[box]-- == 1)
					{
						/* there are now no unread articles */
						box_status_changed = 1;

						if(boxes_unread[box] <= STATUS_UNREAD)
							boxes_unread[box] = new_status;

					}
				}

				if((new_status <= STATUS_UNREAD) && (stat > STATUS_UNREAD))
				{
					if(boxes_unread_copy[box]++ == 0)
					{
						/* there are now some unread articles */
						box_status_changed = 1;

						if(boxes_unread[box] > STATUS_UNREAD)
							boxes_unread[box] = new_status;
					}
				}

			}

			if(new_replied >= 0)
			{
				cptr->status = (cptr->status & ~STATUS_BIT_REPLIED) | new_replied;
			}

			if(new_outgoing >= 0)
			{
				cptr->status = (cptr->status & ~STATUS_BIT_OG) | new_outgoing;
			}

			article_update(fr,cptr,NULL,0,0);
			call_event_process(0);
		}

		if(status == STATUS_NEW)
			cptr->status =  (cptr->status & ~STATUS_MASK) + status;
	}

	if((options.box_show_locked == 0) &&
			(boxes_unread_count[changed_box]==0) && (boxes_unread_copy[changed_box] == 0))
	{
		dont_recount = 1;
	}

	memcpy(boxes_unread_count,boxes_unread_copy,sizeof(boxes_unread_count));
	clear_lock_lists(3);
	redraw_list_lines(fr,0);

	if(changed_box != -1)
	{
		redraw_boxes_list(changed_box);
		if(dont_recount)
			return;
	}

	if(box_status_changed)
	{
		if((changed_box < 0) || (box_table[changed_box].hide_box == 2))
			set_boxlist_extent(7);  /* hide box if no unread messages */
	}
	else
	{
		boxlist_recalc();
	}
}   /* end of list_set_status */





void list_set_score(FOLDREC *fr, int type)
/****************************************/
/* Type: 0  user input, 1 enough for a tick, 2 zero */
{
	CARD *cptr;
	int  score;
	int  flag;


	if(check_lock_lists(0xffff) != 0)
		return;

	score = 0;

	if(type==0)
		sscanf(menu_score_value,"%d",&score);
	else if(type==1)
		score = options.filter_show_tick+1;
	else if(type==2)
	{
		if(options.filter_show_tick < 0)
			score = options.filter_show_tick;
	}

	set_lock_lists(3,fr);

	list_enumerate_start(fr);
	visdelay2_begin();

	while((cptr = list_enumerate_next(fr,fr->ixlist)) != NULL)
	{
		flag = 1;
		if((type==1) && (cptr->score >= score))
			flag = 0;
		if((type==2) && (cptr->score <= score))
			flag = 0;

		if(flag)
		{
			cptr->score = score;

			article_update(fr,cptr,NULL,0,0);
			call_event_process(0);
		}
	}

	redraw_list_lines(fr,0);
	visdelay2_end();
	clear_lock_lists(3);
}   /* end of list_set_score */



void list_unlink_thread(FOLDREC *fr)
/**********************************/
{
	CARD *cptr;
	CARD *cptr1;
	int  count=0;
	int  ref;
	CARD_EXPANDED cardexp;

	if(check_lock_lists(0xffff) != 0)
		return;

	list_enumerate_start(fr);

	while((cptr1 = list_enumerate_next(fr,fr->ixlist)) != NULL)
	{
		cptr = cptr1;
		count++;
	}
	if(count != 1)
	{
		werr(0,"Only one article at a time can be unlinked");
		return;
	}

	unpack_card(cptr,&cardexp);
	cardexp.parent = 0;
	cardexp.n_refs = 0;
	cardfile_update(fr,NULL,&cardexp,1);

	/* sort the open part of the article list */
	ref = fr->open_ref[fr->open_levels-1];
	thread_sort(fr,ref,0);
	list_open_at(fr,(int)cptr,0);

	clear_lock_lists(3);
}   /* end of list_unlink_thread */




void list_status_menu_proc(FOLDREC *fr, char *hit)
/************************************************/
{
	switch(hit[1])
	{
	case 1:   /* Unread */
		list_set_status(fr,STATUS_UNREAD);
		break;

	case 2:   /* New unread */
		list_set_status(fr,STATUS_NEW);
		break;

	case 3:   /* Set Read */
		list_set_status(fr,10);
		break;

	case 4:   /* Lock */
		list_set_status(fr,STATUS_LOCKED);
		break;

	case 5:   /* Mark */
		list_set_status(fr,STATUS_MARKED);
		break;

	case 6:   /* Unlock */
		list_set_status(fr,STATUS_READ);
		break;

	case 7:   /* Set replied */
		list_set_status(fr,20);
		break;

	case 8:   /* Unset replied */
		list_set_status(fr,21);
		break;

	case 9:   /* Set outgoing */
		list_set_status(fr,30);
		break;

	case 10:   /* unset outgoing */
		list_set_status(fr,31);
		break;

	case 11:    /* Add tick */
		list_set_score(fr,1);
		break;

	case 12:    /* Remove tick */
		list_set_score(fr,2);
		break;

	case 13:   /* Unlink from Thread*/
		list_unlink_thread(fr);
		break;

	case 14:   /* Set score */
		list_set_score(fr,0);
		break;
	}
}   /* end of list_status_menu_proc */




void list_remove_attachments(FOLDREC *fr)
/***************************************/
{
	int  count=0;
	int  x;
	CARD *cptr;
	char buf[128];

	CARD_EXPANDED *cardex;
	TEXTR *t;

	CARD_EXPANDED card_expanded;

	if(check_lock_lists(0xffff) != 0)
		return;

	list_enumerate_start(fr);

	while((cptr = list_enumerate_next(fr,fr->ixlist)) != NULL)
	{
		if(cptr->n_cats & STATUS_BIT_ATTACH)
			count++;
	}

	if(count == 0)
	{
		werr(0,"No attachments found");
		return;
	}

	sprintf(buf,"Remove attachments from %d articles ?",count);
	if(query(buf,NULL)==0)
		return;

	if(check_lock_lists(0xffff) != 0)
		return;

	set_lock_lists(3,fr);

	cardex = &card_expanded;
	t = &text_rec_temp;

	list_enumerate_start(fr);
	visdelay2_begin();
	x = 0;

	while((cptr = list_enumerate_next(fr,fr->ixlist)) != NULL)
	{
		if((cptr->n_cats & STATUS_BIT_ATTACH) == 0)
			continue;

		unpack_card(cptr, cardex);
		if(article_read(t,cardex->addr,cardex->docbox,cardex->text_offset,cardex->text_length,0,0,0) < 0)
			continue;

		x++;
		visdelay2_percent(x*100/count);

		text_interpret_article(t,cardex,0);
		t->text_length = t->text_body_end;
		t->attachments = 0;
		cardex->status_other2 &= ~STATUS_BIT_ATTACH;
		t->changed = 1;
		cardfile_update(t->fr,t,cardex,0);

		call_event_process(0);
	}

	visdelay2_end();
	clear_lock_lists(3);
}   /* end of list_remove_attachments */




static void list_set_source_selected(int *hits)
/*********************************************/
{
	char *name_ptr;

	if((list_set_source_value = category_menu_hit(hits,&name_ptr)) != 0)
		dbox_setfield(dbox_set_source,1,name_ptr);
}   /* end of list_set_source_selected */




BOOL list_set_source_raw_handler(dbox d, void *event, void *handle)
/*****************************************************************/
{
	wimp_eventstr *e;
	wimp_mousestr *mouse;

	e = (wimp_eventstr *)event;

	switch (e->e)
	{
	case wimp_EBUT:
		/* click on icon.  get icon number. */
		mouse = &e->data.but.m;
		switch(dbox_menu_field = mouse->i)
		{
		case 1:
			dbox_menu(category_make_menu(fr_set_source,0),list_set_source_selected,mouse);
			return(TRUE);
		}
	}
	return(FALSE);
}   /* end of list_set_source_raw_handler */





void list_set_source_handler(dbox d, void *handle)
/************************************************/
{
	FOLDREC *fr;
	CARD *cptr;
	int  source;
	CARD_EXPANDED cardexp;

	fr = (FOLDREC *)handle;

	if(dbox_get(d) != 0)
	{
		dbox_dispose(&d);
		return;
	}
	dbox_dispose(&d);

	if(check_lock_lists(0xffff) != 0)
		return;
	set_lock_lists(3,fr);

	source = list_set_source_value;

	list_enumerate_start(fr);

	while((cptr = list_enumerate_next(fr,fr->ixlist)) != NULL)
	{
		if(cptr->source != source)
		{
			unpack_card(cptr,&cardexp);
			cardexp.source = source;
			cardfile_update(fr,NULL,&cardexp,0);
			call_event_process(0);
		}
	}

	list_sort(fr,2);
	clear_lock_lists(3);
	redraw_list_lines(fr,0);
}   /* end of list_set_source_handler */




void list_set_source(FOLDREC *fr, int control)
/********************************************/
/* Control: 0 change source */
{
	dbox  d;

	static char *message[1] = {
		"Change Source to:"
	};

	if(check_lock_lists(0xffff) != 0)
		return;

	d = dbox_new("ChangeSrc");
	dbox_set_source = d;
	fr_set_source = &src_fr;
	dbox_eventhandler(d,list_set_source_handler,(void *)fr);
	dbox_raw_eventhandler(d,list_set_source_raw_handler,NULL);
	dbox_setfield(d,2,message[control]);

	dbox_showstatic(d);
}   /* end of list_set_source */




int card_category_action(CARD_EXPANDED *cardex, int cat, int action)
/******************************************************************/
{
	int  i, j;
	int  update = 0;
	int  found = -1;

	if(action != 4)
	{
		/* look for specified category, remove it if present */
		j = 0;
		for(i=0; i<cardex->n_cats; i++)
		{
			if(cardex->cat_codes[i] == cat)
			{
				found = i;
			}
			else
			{
				cardex->cat_codes[j++] = cardex->cat_codes[i];
			}
		}
		cardex->n_cats = j;
	}

	update = 0;
	switch(action)
	{
	case 1:   /* add at start */
		for(i=0; i<cardex->n_cats; i++)
		{
			cardex->cat_codes[i+1] = cardex->cat_codes[i];
		}
		cardex->cat_codes[0] = cat;
		cardex->n_cats++;

		if(found != 0)
			update = 1;
		break;

	case 2:   /* add at end */
		cardex->cat_codes[cardex->n_cats] = cat;
		if(found != cardex->n_cats)
			update = 1;
		cardex->n_cats++;
		break;

	case 3:   /* delete */
		if(found >= 0)
			update = 1;
		break;

	case 4:   /* wipe */
		if(cardex->n_cats > 0)
		{
			cardex->n_cats = 0;
			update = 1;
		}
	}
	return(update);
}   /* end of card_category_action */





void list_set_cat_handler(dbox d, void *handle)
/*********************************************/
{
	int  cat=0;
	FOLDREC *fr;
	CARD *cptr;
	CARD_EXPANDED card_exp;

	fr = (FOLDREC *)handle;

	if(d != NULL)
	{
		if(dbox_get(d) != 0)
		{
			dbox_dispose(&d);
			return;
		}
		dbox_dispose(&d);

		cat = list_set_source_value;
	}

	if(check_lock_lists(0xffff) != 0)
		return;
	set_lock_lists(3,fr);

	list_enumerate_start(fr);

	while((cptr = list_enumerate_next(fr,fr->ixlist)) != NULL)
	{
		unpack_card(cptr,&card_exp);

		if(card_category_action(&card_exp,cat,list_set_source_action))
		{
			cardfile_update(fr,NULL,&card_exp,0);
			call_event_process(0);
		}
	}
	/*   list_sort(fr,2);   */
	clear_lock_lists(3);
	redraw_list_lines(fr,0);
}   /* end of list_set_category */




void list_set_category(FOLDREC *fr, int action)
/*********************************************/
/* Action: 1 add at start, 2 add at end, 3 remove, 4 wipe */
{
	dbox  d;

	if(check_lock_lists(0xffff) != 0)
		return;

	list_set_source_action = action;
	if(action == 4)
	{
		if(query("Delete all categories ?",NULL)==0)
			return;

		list_set_cat_handler(NULL,(void *)fr);
	}
	else
	{
		d = dbox_new("ChangeSrc");

		dbox_set_source = d;
		fr_set_source = &cty_fr;
		dbox_eventhandler(d,list_set_cat_handler,(void *)fr);
		dbox_raw_eventhandler(d,list_set_source_raw_handler,NULL);
		dbox_setfield(d,2,"Category name:");

		dbox_showstatic(d);
	}
}   /* end of list_set_category */






void list_set_subject(FOLDREC *fr, int control)
/*********************************************/
/* Control:  1 change title, 2 change author */
{
	CARD *cptr;
	int  source;
	int  changed;
	CARD_EXPANDED cardexp;
	char buf[81];
	dbox  d;

	static char *message[3] = {
		"", "Change subject", "Change author"
	};

	if(check_lock_lists(0xffff) != 0)
		return;

	set_lock_lists(3,fr);

	d = dbox_new("CatChange");
	dbox_setfield(d,2,message[control]);

	switch(control)
	{
	case 1:
		list_enumerate_start(fr);

		if((cptr = list_enumerate_next(fr,fr->ixlist)) != NULL)
		{
			dbox_setfield(d,1,&cptr->data[cptr->d_title]);
		}
		visdelay2_end();
		break;

	case 2:
		list_enumerate_start(fr);

		if((cptr = list_enumerate_next(fr,fr->ixlist)) != NULL)
		{
			dbox_setfield(d,1,&cptr->data[cptr->d_author]);
		}
		visdelay2_end();
		break;
	}

	dbox_show(d);
	if(dbox_fillin(d) != 0)
	{
		clear_lock_lists(3);
		dbox_dispose(&d);
		return;
	}

	dbox_getfield(d,1,buf,sizeof(buf));
	dbox_dispose(&d);

	list_enumerate_start(fr);

	while((cptr = list_enumerate_next(fr,fr->ixlist)) != NULL)
	{
		changed=0;
		switch(control)
		{
		case 0:
			if(cptr->source != source)
			{
				unpack_card(cptr,&cardexp);
				cardexp.source = source;
				changed=1;
			}
			break;

		case 1:
			if(strcmp(buf,&cptr->data[cptr->d_title]) != 0)
			{
				unpack_card(cptr,&cardexp);
				strcpy(cardexp.title,buf);
				changed=1;
			}
			break;

		case 2:
			if(strcmp(buf,&cptr->data[cptr->d_author]) != 0)
			{
				unpack_card(cptr,&cardexp);
				strcpy(cardexp.author,buf);
				changed=1;
			}
			break;
		}
		if(changed)
		{
			cardfile_update(fr,NULL,&cardexp,0);
			call_event_process(0);
		}
	}

	list_sort(fr,2);
	clear_lock_lists(3);
	redraw_list_lines(fr,0);
}   /* end of list_set_subject */


